<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
<head>
    <th:block th:insert="~{fragments/common :: head(title=#{releases.title}, header=#{releases.title})}"></th:block>
</head>

<body>
<div id="page-container">
    <div id="content-wrap">
        <th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
        <br><br>
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-md-8 bg-card">
                    <div class="tool-header">
                        <span class="material-symbols-rounded tool-header-icon history">update</span>
                        <span class="tool-header-text" th:text="#{releases.header}">Release Notes</span>
                    </div>

                    <div class="alert alert-info" role="alert">
                        <strong th:text="#{releases.current.version}">Current Installed Version</strong>:
                        <span id="currentVersion" th:text="${@appVersion}"></span>
                    </div>

                    <div class="alert alert-warning" role="alert">
                        <span th:text="#{releases.note}">All release notes are only available in English</span>
                    </div>

                    <div id="loading" class="text-center my-4">
                        <div class="spinner-border text-primary" role="status">
                            <span class="visually-hidden">Loading...</span>
                        </div>
                    </div>

                    <div id="error-message" class="alert alert-danger d-none" role="alert">
                        Failed to load release notes. Please try again later.
                    </div>

                    <!-- Release Notes Container -->
                    <div id="release-notes-container" class="release-notes-container">
                        <!-- Release notes will be dynamically inserted here -->
                    </div>
                </div>
            </div>
        </div>
    </div>
    <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>

<style>
    .release-notes-container {
        margin-top: 2rem;
    }

    .release-card {
        border: 1px solid #dee2e6;
        border-radius: 0.25rem;
        margin-bottom: 1.5rem;
        padding: 1rem;
    }

    .release-card.current-version {
        border-color: #28a745;
        background-color: rgba(40, 167, 69, 0.05);
    }

    .release-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 1rem;
    }

    .release-header h3 {
        margin: 0;
        display: flex;
        gap: 1rem;
        align-items: center;
    }

    .version {
        font-weight: bold;
    }

    .release-date {
        color: #6c757d;
        font-size: 0.9em;
    }

    .release-body {
        font-size: 0.9rem;
        white-space: pre-wrap;
        word-break: break-word;
    }

    .release-link {
        color: #0d6efd;
        text-decoration: none;
    }

    .release-link:hover {
        text-decoration: underline;
    }
</style>

<script th:inline="javascript">
    /*<![CDATA[*/

    // Get the current version from the appVersion bean
    const appVersion = /*[[${@appVersion}]]*/ '';

    // GitHub API configuration
    const REPO_OWNER = 'Stirling-Tools';
    const REPO_NAME = 'Stirling-PDF';
    const GITHUB_API = 'https://api.github.com/repos/' + REPO_OWNER + '/' + REPO_NAME;
    const GITHUB_URL = 'https://github.com/' + REPO_OWNER + '/' + REPO_NAME;
    const MAX_RELEASES = 8;

    // Secure element creation helper
    function createElement(tag, attributes = {}, children = []) {
        const element = document.createElement(tag);
        Object.entries(attributes).forEach(([key, value]) => {
            if (typeof value === 'string') {
                element.setAttribute(key, value);
            }
        });
        children.forEach(child => {
            if (typeof child === 'string') {
                element.appendChild(document.createTextNode(child));
            } else if (child instanceof Node) {
                element.appendChild(child);
            }
        });
        return element;
    }

    const ALLOWED_TAGS = {
        'a': ['href', 'target', 'rel', 'class'],
        'img': ['src', 'alt', 'width', 'height', 'style'],
        'br': [],
        'p': [],
        'div': [],
        'span': []
    };

    // Function to safely create HTML elements from string
    function createSafeElement(htmlString) {
        try {
            const parser = new DOMParser();
            const doc = parser.parseFromString(htmlString, 'text/html');

            function sanitizeNode(node) {
                // Safety check for null/undefined
                if (!node) return null;

                // Handle text nodes
                if (node.nodeType === Node.TEXT_NODE) {
                    return node.cloneNode(true);
                }

                // Handle element nodes
                if (node.nodeType === Node.ELEMENT_NODE) {
                    const tagName = node.tagName.toLowerCase();

                    // Check if tag is allowed
                    if (!ALLOWED_TAGS[tagName]) {
                        return document.createTextNode(node.textContent);
                    }

                    // Create new element
                    const cleanElement = document.createElement(tagName);

                    // Copy allowed attributes
                    const allowedAttributes = ALLOWED_TAGS[tagName];
                    Array.from(node.attributes).forEach(attr => {
                        if (allowedAttributes.includes(attr.name)) {
                            let value = attr.value;
                            if (attr.name === 'href' || attr.name === 'src') {
                                try {
                                    value = encodeURI(value);
                                } catch {
                                    return;
                                }
                            }
                            cleanElement.setAttribute(attr.name, value);
                        }
                    });

                    // Add security attributes for links
                    if (tagName === 'a') {
                        cleanElement.setAttribute('rel', 'noopener noreferrer');
                    }

                    // Process children
                    Array.from(node.childNodes).forEach(child => {
                        const cleanChild = sanitizeNode(child);
                        if (cleanChild) {
                            cleanElement.appendChild(cleanChild);
                        }
                    });

                    return cleanElement;
                }

                // If not text or element node, return null
                return null;
            }

            // Get the actual content from the body
            const content = doc.body.children;
            if (!content || content.length === 0) {
                return null;
            }

            // If it's a single element, process it directly
            if (content.length === 1) {
                return sanitizeNode(content[0]);
            }

            // If multiple elements, wrap them in a div
            const wrapper = document.createElement('div');
            Array.from(content).forEach(node => {
                const cleanNode = sanitizeNode(node);
                if (cleanNode) {
                    wrapper.appendChild(cleanNode);
                }
            });

            return wrapper;
        } catch (error) {
            console.error('Error parsing HTML:', error);
            return null;
        }
    }

    function processGitHubReferences(text) {
    if (!text || typeof text !== 'string') {
        return text;
    }

    let currentText = text;
    let match;
    let lastIndex = 0;
    const result = document.createElement('span');
    const urlRegex = new RegExp('https://github\\.com/' + REPO_OWNER + '/' + REPO_NAME + '/(?:issues|pull)/(\\d+)', 'g');

    while ((match = urlRegex.exec(text)) !== null) {
        // Add text before the match
        if (match.index > lastIndex) {
            result.appendChild(document.createTextNode(text.substring(lastIndex, match.index)));
        }

        // Create link element
        const link = document.createElement('a');
        link.href = encodeURI(match[0]);
        link.textContent = `#${match[1]}`; // Use issue/PR number
        link.className = 'release-link';
        link.target = '_blank';
        link.rel = 'noopener noreferrer';
        result.appendChild(link);

        lastIndex = match.index + match[0].length;
    }

    // Add remaining text after last match
    if (lastIndex < text.length) {
        result.appendChild(document.createTextNode(text.substring(lastIndex)));
    }

    return result;
}

// Update formatText function to handle processGitHubReferences properly
function formatText(text) {
    const container = document.createElement('div');

    if (!text || typeof text !== 'string') {
        console.error('Invalid input to formatText:', text);
        return container;
    }

    let textWithoutComments;
    try {
        textWithoutComments = text.replace(
            /<!-- Release notes generated using configuration in .github\/release\.yml at main -->/,
            ''
        );
    } catch (error) {
        console.error('Error in replace operation:', error);
        return container;
    }

    // Split the text into lines
    let lines;
    try {
        lines = textWithoutComments.split('\n');
    } catch (error) {
        console.error('Error in split operation:', error);
        return container;
    }

    let currentList = null;

    lines.forEach(line => {
        const trimmedLine = line.trim();

        // Skip empty lines but add spacing
        if (!trimmedLine) {
            if (currentList) {
                container.appendChild(currentList);
                currentList = null;
            }
            container.appendChild(document.createElement('br'));
            return;
        }

        // Check if the line is HTML
        if (trimmedLine.startsWith('<') && trimmedLine.endsWith('>')) {
            if (currentList) {
                container.appendChild(currentList);
                currentList = null;
            }

            const safeElement = createSafeElement(trimmedLine);
            if (safeElement) {
                container.appendChild(safeElement);
            } else {
                // If HTML parsing fails, treat as plain text
                container.appendChild(document.createTextNode(trimmedLine));
            }
            return;
        }

        // Check for headers
        const headerMatch = trimmedLine.match(/^(#{1,3})\s+(.+)$/);
        if (headerMatch) {
            if (currentList) {
                container.appendChild(currentList);
                currentList = null;
            }
            const headerLevel = headerMatch[1].length;
            const headerContent = headerMatch[2];
            // Process GitHub references in headers
            const processedContent = processGitHubReferences(headerContent);
            const header = createElement(`h${headerLevel}`);
            header.appendChild(processedContent);
            container.appendChild(header);
            return;
        }

        // Check for bullet points
        const bulletMatch = trimmedLine.match(/^[-*]\s+(.+)$/);
        if (bulletMatch) {
            if (!currentList) {
                currentList = document.createElement('ul');
            }

            const listContent = bulletMatch[1];
            const listItem = document.createElement('li');

            // Process GitHub references in list items
            listItem.appendChild(processGitHubReferences(listContent));
            currentList.appendChild(listItem);
            return;
        }

        // If we reach here and have a list, append it
        if (currentList) {
            container.appendChild(currentList);
            currentList = null;
        }

        // Handle regular paragraph
        const paragraph = document.createElement('p');
        paragraph.appendChild(processGitHubReferences(trimmedLine));
        container.appendChild(paragraph);
    });

    // Append any remaining list
    if (currentList) {
        container.appendChild(currentList);
    }

    return container;
}

    const MAX_PREVIOUS_RELEASES = 5;
function compareVersions(v1, v2) {
    const normalize = v => v.replace(/^v/, '');
    const v1Parts = normalize(v1).split('.').map(Number);
    const v2Parts = normalize(v2).split('.').map(Number);

    for (let i = 0; i < Math.max(v1Parts.length, v2Parts.length); i++) {
        const v1Part = v1Parts[i] || 0;
        const v2Part = v2Parts[i] || 0;
        if (v1Part > v2Part) return 1;
        if (v1Part < v2Part) return -1;
    }
    return 0;
}

async function loadReleases() {
    const container = document.getElementById('release-notes-container');
    const loading = document.getElementById('loading');
    const errorMessage = document.getElementById('error-message');

    try {
        loading.classList.remove('d-none');
        errorMessage.classList.add('d-none');

        // Clear container safely
        while (container.firstChild) {
            container.removeChild(container.firstChild);
        }
        const cachedReleases = sessionStorage.getItem('releases');

        let releases;
        if (cachedReleases) {
            releases = JSON.parse(cachedReleases);
            console.log("Read from storage");
        } else {
            const response = await fetch(GITHUB_API + '/releases');
            if (!response.ok) {
                if (response.status === 403) {
                    throw new Error('API rate limit exceeded');
                }
                if (response.status === 404) {
                    throw new Error('Repository not found');
                }
                throw new Error('Failed to fetch releases');
            }
            releases = await response.json();
            sessionStorage.setItem('releases', JSON.stringify(releases));
        }

        // Sort releases by version number (descending)
        releases.sort((a, b) => compareVersions(b.tag_name, a.tag_name));

        // Find index of current version
        const currentVersionIndex = releases.findIndex(release =>
            compareVersions(release.tag_name, 'v' + appVersion) === 0 ||
            compareVersions(release.tag_name, appVersion) === 0
        );

        if (currentVersionIndex === -1) {
            container.appendChild(createElement('div', {
                class: 'alert alert-warning'
            }, ['Current version not found in releases.']));
            return;
        }

        // Get current version and 8 previous releases
        const endIndex = Math.min(currentVersionIndex + MAX_PREVIOUS_RELEASES + 1, releases.length);
        const relevantReleases = releases.slice(currentVersionIndex, endIndex);

        if (relevantReleases.length === 0) {
            container.appendChild(createElement('div', {
                class: 'alert alert-warning'
            }, ['No releases found.']));
            return;
        }

        relevantReleases.forEach((release, index) => {
            const isCurrentVersion = index === 0; // First release in the array is current version

            const releaseCard = createElement('div', {
                class: `release-card ${isCurrentVersion ? 'current-version' : ''}`
            });

            const header = createElement('div', { class: 'release-header' });

            const h3 = createElement('h3', {}, [
                createElement('span', { class: 'version' }, [release.tag_name]),
                createElement('span', { class: 'release-date' }, [
                    new Date(release.created_at).toLocaleDateString()
                ])
            ]);

            header.appendChild(h3);

            if (isCurrentVersion) {
                header.appendChild(createElement('span', {
                    class: 'badge bg-success'
                }, ['Installed']));
            }

            releaseCard.appendChild(header);

            const body = createElement('div', { class: 'release-body' });
            body.appendChild(formatText(release.body || 'No release notes available.'));

            releaseCard.appendChild(body);
            container.appendChild(releaseCard);
        });

    } catch (error) {
        console.error('Error loading releases:', error);
        errorMessage.classList.remove('d-none');
    } finally {
        loading.classList.add('d-none');
    }
}


    // Load releases when the page loads
    document.addEventListener('DOMContentLoaded', loadReleases);

    /*]]>*/
</script>
</body>
</html>
